package risoe.syslab.fhnew.hw.knx;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.concurrent.ConcurrentLinkedQueue;

import tuwien.auto.calimero.exception.KNXException;
import tuwien.auto.calimero.process.ProcessCommunicator;

/**
 * KNX Class providing the ability to do asynchronous writes to KNX devices
 * @author M. Svendsen
 */
public class KNXHandler extends Thread {
	private static final int WRITE_DELAY = 50;

	private final ConcurrentLinkedQueue<KNX_Task> mTasks = new ConcurrentLinkedQueue<KNX_Task>();
	private final Object mTaskLock = new Object();

	@Override
	public void run() {
		while (!Thread.interrupted()){
			synchronized (mTaskLock) {
				// Wait for a new event to be posted
				try {
					mTaskLock.wait();
				} catch (InterruptedException e) {/* Unused */}
			}

			// Check if any events have been posted
			while (!mTasks.isEmpty()){
				KNX_Task task = mTasks.poll();

				// Execute the task. If execute() returns false, the task has been cancelled, meaning that 
				// there is no reason to sleep the thread.
				if (task.execute()){
					// Wait a specified delay before running the next task
					try {
						// Wait for the KNX bus to catch up
						Thread.sleep(WRITE_DELAY);
					} catch (InterruptedException ex) {
						Thread.currentThread().interrupt();
					}
				}
			}
		}
	}
	
	@Override
	public void interrupt() {
		super.interrupt();
		synchronized (mTaskLock) {
			mTaskLock.notify();
		}
	}

	/**
	 * Posts a new task on this thread, returning a handle to the task. This handle can be used to check completion and so forth.
	 * This function is called internally when a call to an actuators 'write' function is executed.
	 * @param device the device that ordered this write
	 * @param value the value to write
	 * @return a reference to the newly created asynchronous task
	 */
	public KNX_Task postTask(KNXDevice device, String value){
		// Create a new task and add it to the task queue
		KNX_Task newTask = new KNX_Task(device, value); 
		mTasks.add(newTask);

		// Unlock the thread if it was waiting
		synchronized (mTaskLock) {
			mTaskLock.notify();
		}

		return newTask;
	}

	/**
	 * @return the number of tasks in the queue of this KNX_Handler
	 */
	public int getQueueSize(){
		return mTasks.size();
	}

	/**
	 * Holder for a pending async write operation
	 * @author M. Svendsen
	 */
	public static class KNX_Task{
		private final String mValue;
		private final KNXDevice mDevice;
		private boolean mCompleted = false;
		private boolean mCancelled = false;

		public KNX_Task(KNXDevice device, String value) {
			mValue = value;
			mDevice = device;
		}

		/**
		 * Called by the handler thread. Do not call this from anywhere else.
		 * @return a boolean indicating whether this task has been executed successfully
		 */
		public boolean execute(){
			if (mCancelled) return false;

			int acknowledged = 0;
			while (acknowledged < KNXConstants.KNX_MAX_RETRIES){
				try {
					ProcessCommunicator comm = mDevice.getInterface().getProcessCommunicator();
					synchronized (comm) {
						comm.write(mDevice.getDataPoint(), mValue);
					}
					mCompleted = true;
					return true;
				} catch (KNXException e) {
					// No acknowledge received. try again
					acknowledged++;
				}
			}

			// If this point is reached, the transmission of the message was unsuccessful!
			mDevice.getInterface().getLogger().logEvent(mDevice.getInterface().getName(), "Transmission Error", 
					"Failed to transmit message to device: "+mDevice.getName()+" at address: "+mDevice.getGroupAddress());
			return true;
		}

		/**
		 * Cancels this asynchronous write
		 */
		public void cancel(){
			mCancelled = true;
		}

		/**
		 * Checks whether this asynchronous task has been run yet
		 * @return a boolean indicating whether this task has been executed.
		 */
		public boolean isFinished(){
			return mCompleted;
		}
	}
}
